package dungeons;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

import squares.Direction;
import squares.IllegalSquareException;
import squares.RockSquare;
import squares.Square;



import be.kuleuven.cs.som.annotate.Model;
import borders.*;
/**
 * a dungeon class that contains a 3D field with squares, to represent a dungeon in the game.
 * a dungeon can be a composite dungeon containing plateaus and/or shafts or a plateau/shaft on itself.
 * @invar
 * 		the maximum range of the 3 directions of the dimension are all bigger or equal then zero and smaller then Long.MAX_VALUE but can't be all zero.
 * 		 | (maxX >= 0 && maxY >= 0 && maxZ >= 0) && !(maxX=maxY=maxZ=0)
 * @invar
 * 		there are no squares location in dimensions with negative values for one of the 3 coordinate values
 * 		|for(long x from -1 to Long.MIN_VALUE)
 * 		|	dimensions.get(x) == null;
 * 		|for(long x from 0 to Long.MAX_VALUE)
 * 		| for(long y  from -1 to Long.MIN_VALUE)
 * 		|	dimensions.get(x).get(y) == null
 * 		| 
 * 		|  for(long x from 0 to Long.MAX_VALUE)
 * 		|   for(long y from 0 to Long.MAX_VALUE)
 * 		|	 for(long z from -1 to Long.MIN_VALUE)
 * 		|    dimensions.get(x).get(y).get(z) == null
 * @invar
 * 		there are no square located on coordinates where the 3 dimension values are the same
 * 		| for(long x from 0 to Long.MAX_VALUE)
 * 		| dimensions.get(x).get(x).get(x) == null
 * @invar 
 * 		all squares located in the squares hashSet can be found in the dimensions hashmap
 * 		| int i = 0;
 * 		| for(Square square: squares)
 * 		|  for(long x from 0 to Long.MAX_VALUE)
 * 		|   for(long y from 0 to Long.MAX_VALUE)
 * 		|	 for(long z from 0 to Long.MAX_VALUE)
 * 		|     if(dimensions.get(x).get(y).get(z) square)
 * 		|			i++;
 * 		|  at end: i == 1;		
 * @param <E>
 * 		this parameter represents the kind of square this dungeon contains.
 * @version 1.0
 * @author Gilles Groven & Jochen Tombal
 */
public abstract class Dungeon<E extends Square> implements Cloneable, Iterable<E>{
	
	private long maxX;
	private long maxY;
	private long maxZ;
	private CompositeDungeon<E> rootDungeon;
	private boolean isTerminated;
	/**
	 * initializes this dungeon with the given values as max values for the dimesions of the dungeon, and with no squares yet present
	 * @param x
	 * 		the max x-coordinate of this dungeon
	 * @param y
	 * 		the max y-coordinate of this dungeon
	 * @param z
	 * 		the max z-coordinate of this dungeon
	 * @throws IllegalArgumentException
	 * 		if one of the given max values of the coordinates are smaller then zero
	 * 		| x < 0 || y <0 || z<0
	 * @post the max x value of this dungeon equals the given x value
	 * 		| new.getmaxX == x
	 * @post the max y value of this dungeon equals the given y value
	 * 		| new.getmaxY == y
	 * @post the max z value of this dungeon equals the given z value
	 * 		| new.getmaxZ == z
	 */
	protected Dungeon(long x, long y, long z) throws IllegalArgumentException
	{
		if(x < 0 || y < 0 || z < 0)
			throw new IllegalArgumentException("invalid dimensions");
		else
		{ 
			this.maxX = x;
			this.maxY = y;
			this.maxZ = z;
			rootDungeon = null;
			this.isTerminated = false;
		}	
	}
	/**
	 * method to change the size of the dungeon dimension in the x-direction. the given Long must allways
	 * be bigger then the current size
	 * @param x
	 * 		the new size of the x dimension
	 * @throws IllegalArgumentException
	 * 		if the given x is smaller then the old x dimension
	 * 		| x < this.getmaxX
	 * @post the new value of the max value of the x-dimension is changed to the given value
	 * 		| new.getmaxX == x		
	 */
	public void setMaxX(long x) throws IllegalArgumentException
	{
		if(x < this.maxX)
			throw new IllegalArgumentException("the Long must be bigger then the current size of x");
		else
		{
			this.maxX = x;
		}
	}
	/**
	 * 
	 * @return
	 * 		the rootDungeon of this dungeon
	 * 		| this.rootDungeon
	 */
	public CompositeDungeon<E> getRootDungeon()
	{
		return this.rootDungeon;
	}
	/**
	 * sets the rootDungeon of this dungeon to the given dungeon
	 * @param dungeon
	 * 			the new RootDungeon of this dungeon
	 * @post
	 * 		the new rootDungeon is the given rootdungeon if it was a valid one.
	 * 		| if(dungeon instanceof CompositeDungeon && ((CompositeDungeon<E>)dungeon).getAllRelatedDungeons().contains(dungeon) && dungeon.getRootDungeon() == null)
	 * 		| 	then new.getRootDungeon() == dungeon
	 */
	protected void setRootDungeon(CompositeDungeon<E> dungeon)
	{
		if(dungeon instanceof CompositeDungeon)
		{
			if(dungeon.getAllRelatedDungeons().contains(dungeon) && dungeon.getRootDungeon() == null)
			{
				this.rootDungeon = dungeon;
			}
		}
			
	}
	/**
	 * 
	 * @return
	 * 		the max X coordinate of this dungeon.
	 */
	public Long getMaxX()
	{
		return this.maxX;
	}
	/**
	 * 
	 * @return
	 * 		the max Y coordinate of this dungeon
	 */
	public Long getMaxY()
	{
		return this.maxY;
	}
	/**
	 * 
	 * @return
	 * 		the max Z coordinate of this dungeon.
	 */
	public Long getMaxZ()
	{
		return this.maxZ;
	}

	/**
	 * expands the dimensions hashmap with the new value for the y-direction, always beeing larger then the old one.
	 * @param y
	 * 		the new max value for the y direction.
	 * @throws IllegalArgumentException
	 * 		if the given y is smaller then the old y dimension
	 * 		| y < this.maxY
	 * @post the given y value equals the max y value of this dungeon if the given y is valid
	 * 		| y == new.maxY
	 */
	public void setMaxY(long y) throws IllegalArgumentException
	{
		if(y < this.maxY)
			throw new IllegalArgumentException("the Long must be bigger then the current size of y");
		else
		{
			this.maxY = y;
		}	
	}

	/**
	 * expands the dimensions hashmap values his values to the new given value for z, and this for all x and y.
	 * @param z
	 * 		the new max value for the z dimension
	 * @throws IllegalArgumentException
	 * 		if the given z is smaller then the old z dimension
	 * 		| z< maxZ
	 * @post the max z value of this dungeon is changed to the given value if the given value  is valid.
	 * 		| y == new.getmaxY
	 */		
	public void setMaxZ(long z) throws IllegalArgumentException
	{
		if(z < this.maxZ)
			throw new IllegalArgumentException("the Long must be bigger then the current size of Z");
		else
		{
			this.maxZ = z;
		}
	}
	/**
	 * adds the given square to the dungeon on the specified position
	 * @param square
	 * 			the square you want to add on the specified position
	 * @param x
	 * 		the x-coordinate of the position
	 * @param y
	 * 		the y-coordinate of the position
	 * @param z
	 * 		the z-coordinate of the position
	 * @throws IllegalSquareException
	 * 		if the square is null or can not be added because it is already present in the dungeon.
	 * 		also if the given square is already attached to other squares.
	 * 		| if(square == null || squares.contains(square) || duplicateCoordinate(x,y,z))
	 * 		|		then(exception)
	 * 		| for(Direction direction: Direction.Values())
	 * 		| if(!square.getBorderAt(direction).hasOnlyOneAttached())
	 * 		|	then(exception)
	 * @throws IllegalArgumentException
	 * 		if the coordinates are not valid (one or more below zero), out of range of the dimensions, all the same.
	 * 		also if adding this square will give the dungeon more the 20% squares that are slippery.	
	 * 		also if adding the square on the given position will result in borders that are not allowed by the program.
	 * 		| if((x== y && y==z) ||x > maxX || y> maxY || z>maxZ || x<0 || y<0 || z<0)
	 * 		| if(square.getSlipperyFloor() && toManySlippery())
	 * 		| if(duplicateCoordinate(x,y,z))
	 * 		| if(!willHaveValidBorders(square,x,y,z))
	 * @post
	 * 		the given square is added on the given position
	 * 		|getSquareAt(x,y,z) == square 
	 * @post
	 * 		the temperature and humidity of the square is adjusted if it has become part of a space.
	 * 		|square.adjustHumiditySpace(this)
	 * 		|square.adjustTemperatureSpace(this)	
	 */
	public abstract void addSquare(E square, long x, long y, long z) throws IllegalSquareException,IllegalArgumentException;

	/**
	 * checks if there is already a square present on the given coordinate.
	 * @param x
	 * 		the x value of the coordinate.
	 * @param y
	 * 		the y value of the coordinate.
	 * @param z
	 * 		the z value of the coordinate.
	 * @return
	 * 		true if there is already a square present.
	 * 		|if(dimensions.get(x).get(y).get(z) != null)
	 * 		| then(result == true)
	 */
	protected abstract boolean duplicateCoordinate(long x,long y,long z);
	
	/**
	 * checks if a square that you want to add will be valid for this dungeon.
	 * @param square
	 * 		the square you want to check
	 * @param x
	 * 		the x value of the coordinate where you want to add the square.
	 * @param y
	 * 		the y value of the coordinate where you want to add the square.
	 * @param z
	 * 		the z value of the coordinate where you want to add the square.
	 * @return
	 * 		true if the square will have valid borders,
	 * 		meaning if it is a transparent square, at most 2 doors in opposite directions, and with no walls. if RocksSquare, only Walls,
	 * 		if regular Square, no doors in floor, at least one door and at most 3 doors and atleast.
	 * 		and all adjacent squares will still have valid borders.
	 *		|if( border is added)
	 *		| for(Square square: squares)
	 *		|	for(Direction direction: Direction.values())
	 *		|  		square.new.borderCanBeChanged(direction,square.getBorderAt(direction))
	 * 
	 */
	@Model
	protected abstract boolean willHaveValidBorders(E square,long x,long y,long z);
	
	/**
	 * Returns the border of the type that will be chosen above the other border when two squares are merged.
	 * wall > door > open
	 * @param 	border1
	 * 			the first border that is to be checked if its type is dominant
	 * @param	border2
	 * 			the second border that is to be checked if its type is dominant
	 * @return 	the border which type is more dominant than the other (walls over doors, doors over open spaces)
	 * 			if both are walls,the one that is slippery is given, and if none is slippery, then the first is given
	 * 			|if(((Wall)border1).getSlipperyOfWall())
	 *			|{
	 *			|	then (result == border1)
	 *			|}
	 *			|	else if(((Wall)border2).getSlipperyOfWall())
	 *			|	{
	 *			|	then (result ==border2 )
	 *			|	}
	 *			|	else
	 *			|	{
	 *			|		(then result ==border1)
	 *			|	}
	 *			|if(border1 instanceof Wall || border2 instanceof Wall)
	 *			| then(result == border1)
	 *			|if(border1 instanceof Door && border2 instanceof Open)
	 *			| then(result == border1)
	 *			|if(border2 instanceof Door && border1 instanceof Open)
	 *			| then(result == border2)
	 *			|else
	 *			| then(result == border1)	
	 */
	protected Border getMostImportantBorder(Border border1, Border border2) {
		if(border1 instanceof Wall && border2 instanceof Wall)
		{
			if(((Wall)border1).getSlipperyOfWall())
			{
				return border1;
			}
			else if(((Wall)border2).getSlipperyOfWall())
			{
				return border2;
			}
			else
			{
				return border1;
			}
		}
		if(border1 instanceof Wall)
			return border1;
		if(border2 instanceof Wall)
			return border2;
		if(border1 instanceof Door && border2 instanceof Open)
			return border1;
		if(border2 instanceof Door && border1 instanceof Open)
			return border2;
		else
			return border1;	
	}
	
	/**
	 * merges a square his borders with its potential neighbors. 
	 * 
	 * @param square
	 * 			the square which borders has to be merged with its potential neighbors
	 * @param x
	 * 			the x value of the square its coordinate
	 * @param y
	 * 			the y value of the square its coordinate
	 * @param z
	 * 			the z value of the square its coordinate
	 * @throws IllegalArgumentException
	 * 			throws this exception if invalid input is given for the coordinates
	 * 			| x>maxX || y> maxY || z> maxZ || x<0 || y<0 || z<0 || (x==y && y==z)
	 * @throws IllegalSquareException 
	 * 			throws this exception if the given square is null
	 * 			| square == null;
	 * @post	the square has shared borders with the squares located at positions around it.
	 * 			|  square.new.getBorderAt(Direction.CEILING).equals(getSquareAt(x,y,z+1).getBorderAt(Direction.FLOOR) || square.new.getBorderAt(Direction.CEILING).getOtherAttachedSquare(square) == null
	 * 			|  square.new.getBorderAt(Direction.FLOOR).equals(getSquareAt(x,y,z-1).getBorderAt(Direction.CEILING) || square.new.getBorderAt(Direction.FLOOR).getOtherAttachedSquare(square) == null
	 * 			|  square.new.getBorderAt(Direction.NORTH).equals(getSquareAt(x,y+1,z).getBorderAt(Direction.SOUTH) || square.new.getBorderAt(Direction.NORTH).getOtherAttachedSquare(square) == null	 
	 * 			|  square.new.getBorderAt(Direction.SOUTH).equals(getSquareAt(x,y-1,z).getBorderAt(Direction.NORTH) || square.new.getBorderAt(Direction.SOUTH).getOtherAttachedSquare(square) == null
	 * 			|  square.new.getBorderAt(Direction.EAST).equals(getSquareAt(x+1,y,z).getBorderAt(Direction.WEST) || square.new.getBorderAt(Direction.EAST).getOtherAttachedSquare(square) == null
	 * 			|  square.new.getBorderAt(Direction.WEST).equals(getSquareAt(x-1,y,z).getBorderAt(Direction.EAST) || square.new.getBorderAt(Direction.WEST).getOtherAttachedSquare(square) == null	 
	 */
	protected abstract void mergeBorders(E square, long x,long y, long z) throws IllegalArgumentException, IllegalSquareException;
	
	/**
	 * a method to find a square on a specific location in the dungeon
	 * @param x
	 * 		the x value of the coordinate you want to find.
	 * @param y
	 * 		the y value of the coordinate you want to find.
	 * @param z
	 * 		the z value of the coordinate you want to find.
	 * @return
	 * 		the square found on the coordinate, or zero if none was found.
	 * 		|if(dimensions.get(x) == null)
	 *		| 	then(result == null)
	 *		|else if(dimensions.get(x).get(y) == null)
	 *		|	then(result == null)
	 *		|else
	 *		|	 then(result == dimensions.get(x).get(y).get(z))
	 */
	public abstract E getSquareAt(long x, long y, long z);
	
	
	/**
	 * looks for all the squares that are connected to the given square by open borders or open doors,
	 * @param square
	 * 			the square you want to find the space of
	 * @throws	IllegalArgumentException
	 * 			if the given square is invalid
	 * 			|square == null
	 * 
	 * @return	a hashmap containing two different maps : a "squares" key with all the squares in the space 
	 * 			and a "rocks" key with all the rocks surrounding the space.
	 * 			
	 * 			|HashSet<Square> toCheck = new HashSet<Square>()
	 *			|HashSet<Square> checked = new HashSet<Square>()
	 *			|HashSet<Square> rocks = new HashSet<Square>()
	 *			| toCheck.add(square)
	 *			|for (every square : toCheck)
	 * 			| 	for(every border)
	 * 			|		if(connectedSquare instanceof RockSquare)
	 * 			|			if(!rocks.contains(connectedSquare))
	 * 			|				then rocks.add(connectedSquare)
	 * 			|		if(border instanceof Open || instanceOf Open door)
	 * 			|			if(!checked.contains(connectedSquare))
	 * 			|				then checked.add(connectedSquare)
	 * 			|		if(!toCheck.contains(connectedSquare))	
	 * 			|			then toCheck.add(connectedSquare)
	 * 			|	toCheck.remove(square)
	 * 			|
	 * 			|HashMap<String,HashSet<Square>> map = new HashMap<String,HashSet<Square>>();
	 *			|	map.put("squares", checked);
	 *			|	map.put("rocks", rocks);
	 *			|result = map
	 */
	public static HashMap<String,HashSet<Square>> findSpace(Square square)
	{
		HashSet<Square> toCheck = new HashSet<Square>();
		HashSet<Square> checked = new HashSet<Square>();
		HashSet<Square> rocks = new HashSet<Square>();
		if(square == null)
			throw new IllegalArgumentException("invalid square");
		if(square instanceof RockSquare)
		{
			checked.add(square);
			rocks.add(square);
			HashMap<String,HashSet<Square>> map = new HashMap<String,HashSet<Square>>();
			map.put("squares", checked);
			map.put("rocks", rocks);
			return map;
		}
		else
		{

			toCheck.add(square);
			while(!(toCheck.size() == 0))
			{
				HashSet<Square> checkAid = new HashSet<Square>();
				Iterator<Square> it = toCheck.iterator();
				while(it.hasNext())
				{ Square checker = it.next();
				for(Direction direction: Direction.values())
				{
					if(checker.getBorderAt(direction).getOtherAttachedSquare(checker) instanceof RockSquare )
					{
						rocks.add(checker.getBorderAt(direction).getOtherAttachedSquare(checker));
					}
					if(checker.getBorderAt(direction) instanceof Open) 
					{
						if(checker.getBorderAt(direction).getOtherAttachedSquare(checker) != null)
						{
							if(!checked.contains(checker.getBorderAt(direction).getOtherAttachedSquare(checker)) )
							{
								checkAid.add(checker.getBorderAt(direction).getOtherAttachedSquare(checker));
							}
						}
					}
					if(checker.getBorderAt(direction) instanceof Door)
					{
						if(checker.getBorderAt(direction).getOtherAttachedSquare(checker) != null)
						{
							if(((Door)checker.getBorderAt(direction)).getIsOpen())
							{
								if(!checked.contains(checker.getBorderAt(direction).getOtherAttachedSquare(checker)))
								{
									checkAid.add(checker.getBorderAt(direction).getOtherAttachedSquare(checker));
								}
							}
						}
					}
				}
				it.remove();
				checked.add(checker);
				}
				toCheck.addAll(checkAid);
				
			}
		}
		HashMap<String,HashSet<Square>> map = new HashMap<String,HashSet<Square>>();
		map.put("squares", checked);
		map.put("rocks", rocks);
		return map;
	}
	/**
	 * checks if it is possible to certainly walk from the given square to the other square,without beeing teleported away from the space
	 * @param start
	 * 		the square where you want to start
	 * @param destination
	 * 		the square you want to reach
	 * @return
	 * 		true if there is no square that will teleport you away on every possible route to the destionation
	 * 		| if(start == destination)
	 * 		| then result == true
	 * 		|if(findspace(start).contains(end)
	 * 		|	if( for each possible square: findspace(start).contains(square.teleportableSquares())
	 * 		|  && canCertainlyGetFromTo(square,destination)
	 */
	public static boolean canCertainlyGetFromTo(Square start,Square destination)
	{
		HashSet<Square> toCheck = new HashSet<Square>();
		HashSet<Square> checked = new HashSet<Square>();
		HashSet<Square> rocks = new HashSet<Square>();
		if(start == null || destination == null)
			return false;
		if(start == destination)
		{
			return true;
		}
		else
		{

			toCheck.add(start);
			while(!(toCheck.size() == 0))
			{
				HashSet<Square> checkAid = new HashSet<Square>();
				Iterator<Square> it = toCheck.iterator();
				while(it.hasNext())
				{ Square checker = it.next();
				for(Direction direction: Direction.values())
				{
					if(checker.getBorderAt(direction).getOtherAttachedSquare(checker).teleportpossible())
					{
						Square teleportSquare = checker.getBorderAt(direction).getOtherAttachedSquare(checker);
						if(teleportSquare.getTeleportableSquares().size() == 1)
						{
							if(findSpace(start).get("squares").contains(teleportSquare.teleport()))
							{
								if(!checked.contains(teleportSquare) && !toCheck.contains(teleportSquare))
								{
									if(teleportSquare == destination)
									{
										return true;
									}
									checkAid.add(teleportSquare);
								}
							}
						}
					}
					else
					{
					if(checker.getBorderAt(direction).getOtherAttachedSquare(checker) instanceof RockSquare )
					{
						rocks.add(checker.getBorderAt(direction).getOtherAttachedSquare(checker));
					}
					if(checker.getBorderAt(direction) instanceof Open) 
					{
						
						if(checker.getBorderAt(direction).getOtherAttachedSquare(checker) != null)
						{
							if(checker.getBorderAt(direction).getOtherAttachedSquare(checker) == destination)
							{
								return true;
							}
							if(!checked.contains(checker.getBorderAt(direction).getOtherAttachedSquare(checker)) )
							{
								checkAid.add(checker.getBorderAt(direction).getOtherAttachedSquare(checker));
							}
						}
					}
					if(checker.getBorderAt(direction) instanceof Door)
					{
						
						if(checker.getBorderAt(direction).getOtherAttachedSquare(checker) != null)
						{
							if(((Door)checker.getBorderAt(direction)).getIsOpen())
							{
								if(checker.getBorderAt(direction).getOtherAttachedSquare(checker) == destination)
								{
									return true;
								}
								if(!checked.contains(checker.getBorderAt(direction).getOtherAttachedSquare(checker)))
								{
									checkAid.add(checker.getBorderAt(direction).getOtherAttachedSquare(checker));
								}
							}
						}
					}
					}
				}
				it.remove();
				checked.add(checker);
				}
				toCheck.addAll(checkAid);
				
			}
		}
		return false;
	}
	/**
	 * deletes a square completely from the dungeon.
	 * @param x
	 * 		the x coordinate of the square you want to remove
	 * @param y
	 * 		the y coordinate of the square you want to remove
	 * @param z
	 * 		the z coordinate of the square you want to remove
	 * @post
	 * 		the square is removed from the dungeon and there is no reference left to the square
	 * 		| new.getSquareAt(x,y,z) == null
	 * 		| for( all existing Borders)
	 * 		|  if(this.getSquareAt(x,y,z) != null)
	 * 		| {
	 * 		|	then new.getAttachedSquare1() != this.getSquareAt(x,y,z)
	 * 		|   and then new.getAttachedSquare2() != this.getSquateAt(x,y,z)
	 * 		|	}
	 * 		| new.getSquareAt(x,y,z) == null && !new.getAllSquares().contains(this.getSquareAt(x,y,z))
	 */
	public abstract void deleteSquare (long x, long y, long z);
	
	/**
	 * checks if the two given squares are connected threw open doors and open spaces via other squares.
	 * @param start
	 * 			the first square 
	 * @param end
	 * 			the square you want to find if it is connected to the first square
	 * @return
	 * 			true if the squares are connected threw open doors and open spaces via other squares.
	 * 			|if(findSpace(start).contains(end)
	 * 			| then(result == true)
	 * @throws IllegalArgumentException
	 * 		if one of the squares are invalid
	 * 		| start == null || end == null
	 * @note time complexity: O(n)
	 */
	public static boolean isConnected(Square start,Square end) throws IllegalArgumentException
	{
		if(start == null || end == null)
			throw new IllegalArgumentException("invalid square");
		if(findSpace(start).get("squares").contains(end))
		{
			return true;
		}
		else
			return false;
	}
	/**
	 *
	 * @return
	 * 		 the hashset containing all the squares in the dungeon.
	 * 
	 */
	@Model
	protected abstract HashSet<E> getAllSquares();
	/**
	 * 
	 * this method checks if it is possible(not certain!) to access a other (given) square from the given square,using spaces and teleportation.
	 * @param start
	 * 		the square from which you want to start to get to the other square
	 * @param end
	 * 		the square you want to reach
	 * @return
	 * 		true if it is possible to get to the other square (by chance our certainty)
	 * 		it is possible if the start square equals the end square, the end square is in the same space as the start square,
	 * 		the you can teleport to the end square from the start square, or if you can get to the end square
	 * 		from a square in the space of the start square or a square where you can teleport to from the start square.
	 * 		|if(findSpace(start).get("squares").contains(end))
	 * 		|	then result == true
	 * 		| if(start.teleportPossible())
	 * 		|	if(start.getAllTeleportableSquare().contains(end)
	 * 		|		then result == true
	 * 		| if(start == end)
	 * 		|		then result == true
	 * 		|if(start.teleportPossible())
	 * 		| 		if( for square in(start.getAllTeleportableSquare() union findSpace(start).get("squares")
	 * 		|				if(canGetTo(square,end))
	 * 		|					then result == true
	 * 		| if(!start.teleportPossible())
	 * 		|    if( for square in findSpace(start).get("squares"))
	 * 		|		 if(canGetTo(square,end))
	 * 		|				then result == true
	 */
	public static boolean canGetTo(Square start, Square end)
	{
		if(start == null || end == null)
		{
			return false;
		}
		if( start instanceof RockSquare || end instanceof RockSquare)
		{
			return false;
		}
		else
		{
			HashSet<Square> toCheck = new HashSet<Square>();
			HashSet<Square> connected = new HashSet<Square>();
			toCheck.add(start);
			while(!(toCheck.isEmpty()))
			{
				HashSet<Square> checkAid = new HashSet<Square>();
				Iterator<Square> it = toCheck.iterator();
				while(it.hasNext())
				{ Square checker = it.next();
					if(checker.teleportpossible())
					{
						for(Square teleportSquare:checker.getTeleportableSquares())
						{
							if(!connected.contains(teleportSquare) && !toCheck.contains(teleportSquare))
							{
								checkAid.add(teleportSquare);
							}
						}
					}
					for(Direction direction: Direction.values())
					{
						if(checker.getBorderAt(direction) instanceof Open) 
						{
							if(checker.getBorderAt(direction).getOtherAttachedSquare(checker) != null)
							{
								if(!connected.contains(checker.getBorderAt(direction).getOtherAttachedSquare(checker)) && !toCheck.contains(checker.getBorderAt(direction).getOtherAttachedSquare(checker)))
								{
									checkAid.add(checker.getBorderAt(direction).getOtherAttachedSquare(checker));
								}
							}
						}
						if(checker.getBorderAt(direction) instanceof Door)
						{
							if(checker.getBorderAt(direction).getOtherAttachedSquare(checker) != null)
							{
								if(((Door)checker.getBorderAt(direction)).getIsOpen())
								{
									if(!connected.contains(checker.getBorderAt(direction).getOtherAttachedSquare(checker)) && !toCheck.contains(checker.getBorderAt(direction).getOtherAttachedSquare(checker)))
									{
										checkAid.add(checker.getBorderAt(direction).getOtherAttachedSquare(checker));
									}
								}
							}
						}
					}
				it.remove();
				connected.add(checker);
				}
				toCheck.addAll(checkAid);
			}
			if(connected.contains(end))
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		
	}
	public abstract Iterator<E> iterator();
	
	/**
	 * deletes a dungeon and its squares
	 * 
	 * @post  The composite dungeon, if any, is unset from this dungeon
	 *        | this.rootDungeon == null
	 * @post
	 * 		the dungeon is terminated
	 * 		| new.isTerminated() == true
	 */
	public abstract void Terminate();
	/**
	 * 
	 * @return
	 * 		true if this dungeon has been terminated.
	 * 		|result == this.isTerminated
	 */
	public boolean isTerminated()
	{
		return isTerminated;
	}
	
	/**
	 * 
	 * @return
	 * 		the rocks having a temperature higher then or equal to 200 degrees that are located in this dungeon.
	 */
	public abstract HashSet<RockSquare> getRocksOf200Degrees();
    
    /**
     * returns all the squares in this dungeon that satisfy the given methods
     * @param methods
     * 		the methods of square the given squares must validate as a hashmap, method is the key, object is the parameter to check.
     * @return
     * 		all the squares that satisfy the constraints
     * 		|if(result.contains(square)
     * 		| then for all methods:
     * 		| if parameter:
     * 		|	method.invoke(square,methods.get(method)) == true
     * 		|else
     *		|  method.invoke(square) == true
     */
    public Set<E> getAllSquaresWhere(HashMap<Method,Object> methods) 
    {
    	if(methods == null)
    		return null;
    	HashSet<E> foundSquares = new HashSet<E>();
    	for(Method method: methods.keySet()) 
    	{
            if(method.getReturnType() != boolean.class)
            {
            	System.out.println("false boolean");
            	return foundSquares;
            }
            if(! (Arrays.asList(Square.class.getMethods()).contains(method)))
            {
            	System.out.println("no such method");
            	return foundSquares;
            }
            if(method.getParameterTypes().length != 1 )
            {
            	if(method.getParameterTypes().length == 0)
            	{
            		
            	}
            	else
            	{
            		System.out.println(" length >1");
            		return foundSquares;
            	}
            }
            if(method.getParameterTypes().length != 0)
        	{
        		
        	
            	if(method.getParameterTypes()[0].getClass().equals(methods.get(method).getClass().getName()))
            	{
            		return foundSquares;
            	}
        	}

                            
       }
    	
        Iterator<E> it = iterator();
       while(it.hasNext()) 
       {
           E square = it.next();
            boolean succes = true;
            for(Method method:methods.keySet())
            {
            	try {
            		if(method.getParameterTypes().length == 0)
            		{
            			if(!(Boolean)method.invoke(square))
    					{
                			succes = false;
                			break;
    					}
            		}
            		else
            		{
            		if(!(Boolean)method.invoke(square, methods.get(method)))
					{
            			succes = false;
            			break;
					}
            		}
            		} catch (IllegalAccessException e) {
            			e.printStackTrace();
            		} catch (InvocationTargetException e) {
            			e.printStackTrace();
            		}
            }
    		if(succes)
    		{
    			foundSquares.add(square);
    		}
       }
       return foundSquares;
    }    
}


